package com.sap.apitest2.service;

import java.io.BufferedReader;
import java.io.FileReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.apitest2.entity.Param;
import com.sap.apitest2.entity.Result;


public class ResultEvaluator {
	private static int RESULT_OK = 1;
	private static int RESULT_FAIL_STATUSCODE = 2; //failed due to non-200 status code returned from server
	private static int RESULT_FAIL_MISMATCH = 3;
	private static Pattern STOP_CHAR_PATTERN = Pattern.compile("[ \n\",:\\]}]");
	private static String PARAM_ANY = "<%_ANY_%>";
	private static String PARAM_RECENTLY = "<%_RECENTLY_%>";
	
	static class MatchRange {
		public int start;//, end;
		public int len;
		public MatchRange(int i_start, int i_len) {
			this.start = i_start;
			this.len = i_len;
		}
	}
	
	static class RegexMatcher {
		SimpleDateFormat dateFormatter= new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
		Date now;
		public List<MatchRange> bestMatchedRanges; //the most matched parts
		private Pattern[] patterns;
		private List<MatchRange> matchedRanges;
		public boolean match( Pattern[] patterns, String act ) {
			this.patterns = patterns;
			this.bestMatchedRanges = new ArrayList<MatchRange>();
			this.matchedRanges = new ArrayList<MatchRange>();
			boolean matched = this.matchByRegex(0, act);
			//post-process bestMatchedRanges: correct *start* of ranges
			for(int i=bestMatchedRanges.size()-1; i>=0; i--) {
				MatchRange rng1= bestMatchedRanges.get(i);
				int startDiff = rng1.start + rng1.len;
				for(int j=i+1; j<bestMatchedRanges.size(); j++) {
					bestMatchedRanges.get(j).start += startDiff;
				}
			}
			return matched;
		}
		
		private boolean matchByRegex( int cursor, String act ) {
			try {
				now = dateFormatter.parse("2011-10-11T10:00:29Z");
			} catch (ParseException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			now = new Date();
			int start = 0;
			Matcher matcher = patterns[cursor].matcher( act );
			//List<MatchRange> res = new ArrayList<MatchRange>();
			while( matcher.find(start) ) {
				start = matcher.end(); //for next loop
				//check <%_RECENTLY_%>
				boolean dateMatched = true;
				for (int i=0; i<matcher.groupCount(); i++) {
					String dateStr = matcher.group(i+1);
					System.out.println("group " + (i+1) + "=" + matcher.group(i+1));
					try {
						Date d = dateFormatter.parse(dateStr);
						long diff = (now.getTime() - d.getTime())/1000;
						if (diff > 60*2) { //more than 2 minutes
							dateMatched = false;
							break;
						}
						System.out.println(d);
					} catch (ParseException e) {
						logger.error(e.getMessage());
						e.printStackTrace();
					}
				}			
				if (!dateMatched) continue;
				//remove all ranges added by pattern(s) behind, and ad current matched range
				MatchRange rng = new MatchRange(matcher.start(), matcher.end()-matcher.start());
				for(int i=matchedRanges.size()-1; i>=cursor; i--) {
					matchedRanges.remove(i);
				}
				matchedRanges.add(rng);
				//whether it's better matched
				if ( matchedRanges.size()>bestMatchedRanges.size() ) {
					bestMatchedRanges.clear();
					bestMatchedRanges.addAll(matchedRanges);
				}
				//match further pattern
				String subAct = act.substring(matcher.end(), act.length());
				if ( cursor == patterns.length-1 ) { //current pattern is the last one
					return true;
				}
				boolean matched = matchByRegex(cursor+1, subAct);
				if ( matched ) {
					return true;
				}
			}
			return false;			
		}
	}
	
	

	final static Logger logger = LoggerFactory.getLogger(ResultEvaluator.class);

	public void evaluate(Result result, int statusCode, String i_exp, String i_act, Collection<Param> params) {
		i_exp = i_exp.replaceAll("\r\n", "\n");
		i_act = i_act.replaceAll("\r\n", "\n");
		result.setStatus(statusCode);
		//replace param with value
		StringBuffer sbExp = new StringBuffer(i_exp);
		for(Param param : params) {
			String name = "<%" + param.getName() + "%>";
			if (PARAM_ANY.equals(name) || PARAM_RECENTLY.equals(name)) continue;
			String val = param.getValue();
			int len = name.length();
			while(true) {
				int i1 = sbExp.indexOf(name);
				if (i1<0) break;
				sbExp.replace(i1, i1+len, val);
			}
		}
		
		String exp = sbExp.toString();
		result.setExpectedResponse(exp); 
		result.setResponse(i_act);
		
		if (statusCode/200 != 1) { //not 20x code
			 result.setResult(RESULT_FAIL_STATUSCODE);
			 return;
		}
		
		//compare
		//logger.warn(i_act);
		if (!exp.equals(i_act)) {
			result.setResult(RESULT_FAIL_MISMATCH);
			if (exp.indexOf(PARAM_ANY)>=0 || exp.indexOf(PARAM_RECENTLY)>=0) {
				if (!locateMismatchByRegex(result)) result.setResult(RESULT_OK);
			}
			else {
				if (!evaluateNLocateMismatchByFormat(result)) result.setResult(RESULT_OK);
			}
			return;
		}
		result.setResult(RESULT_OK);
	}
	
	/*
	 * true - found mismatched part(s)
	 */
	protected boolean evaluateNLocateMismatchByFormat(Result result) {
		logger.info("evaluateNLocateMismatchByFormat: Normal");
		locateMismatchByChar(result);
		return true;
	}
	
	private static boolean locateMismatchByRegex_v01(Result result) {
		String exp = result.getExpectedResponse();
		String act = result.getResponse();		
		StringBuffer sbRegex = new StringBuffer();
		String paramAny = "<%_ANY_%>";
		String reAny = ".*";
		String[] blocks = exp.split(paramAny, -1);
		for (String block : blocks) {
			block = Pattern.quote(block);
			sbRegex.append(block).append(reAny);
		}
		sbRegex.delete(sbRegex.length()-reAny.length(), sbRegex.length());
		String regex = sbRegex.toString();
		//System.out.println(regex);
		Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(act);
		if (matcher.find()) {
			return false;
		}
		else {
			return true;
		}
	}

	private static boolean locateMismatchByRegex_v2(Result result) {
		String exp = result.getExpectedResponse();
		String act = result.getResponse();		
		StringBuffer sbRegex = new StringBuffer();
		String paramAny = "<%_ANY_%>";
		String reAny = ".*";
		String paramRecently = "<%_RECENTLY_%>";
		String reRecently = "([^ '])";
		String[] b1s = exp.split(paramAny, -1);
		for (String b1 : b1s) {
			String[] b2s = b1.split(paramRecently, -1);
			for (String b2 : b2s){
				b2 = Pattern.quote(b2);
				sbRegex.append(b2).append(reRecently);
			}
			sbRegex.delete(sbRegex.length()-reRecently.length(), sbRegex.length());
			sbRegex.append(reAny);
		}
		sbRegex.delete(sbRegex.length()-reAny.length(), sbRegex.length());
		String regex = sbRegex.toString();
		//System.out.println(regex);
		Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(act);
		if (matcher.find()) {
			return false;
		}
		else {
			return true;
		}
	}
	
	@Deprecated //refer to RegexMatcher
	private static List<MatchRange> matchByRegex(Pattern[] patterns, int patternIdx, String act) {
		int start = 0;
		Matcher matcher = patterns[patternIdx].matcher( act );
		List<MatchRange> res = new ArrayList<MatchRange>();
		while( matcher.find(start) ) {
			start = matcher.end();
			String subAct = act.substring(matcher.end(), act.length());
			if ( patternIdx == patterns.length-1 ) {
				MatchRange rng = new MatchRange(matcher.start(), matcher.end()-matcher.start());
				res.add(rng);
				return res;
			}
			List<MatchRange> subRes = matchByRegex(patterns, patternIdx+1, subAct);
			if ( subRes.size()>0 ) {
				MatchRange rng = new MatchRange(matcher.start(), matcher.end()-matcher.start());
				res.add(rng);
				for ( MatchRange subRng : subRes ) {
					subRng.start += matcher.end();
					res.add(subRng);
				}
				return res;
			}
		}
		return res;
	}
	
	/*
	 * true - found mismatched part(s)
	 */
	private static boolean locateMismatchByRegex(Result result) {
		/*
		 * 1. split by "<%_ANY_%>" into multiple parts, **TODO: and get rid of the first and last parts**
		 * 2. start from first part, do
		 * 		2.1 try to find a match
		 * 			2.1.1 success
		 * 				a current part is not last part, move to next part and go to 2.1 again, otherwise
		 * 				b return no (matched)
		 * 			2.1.2 fail 
		 * 				a current part is not first part, move back to previous part and go to 2.1 again, otherwise
		 * 				b return yes (mismatch)
		 */
		String exp = result.getExpectedResponse();
		String act = result.getResponse();		
		String paramAny = "<%_ANY_%>";
		//String reAny = ".*";
		String paramRecently = "<%_RECENTLY_%>";
		String reRecently = "([^ '\"]*?)";
		String[] arrPart = exp.split(paramAny, -1);
		int iPart = 0, len = arrPart.length;
		StringBuffer sbRegex = new StringBuffer();	
		//prepare regex for each ANY part
		//Pattern[] patterns = new Pattern[arrPart.length];
		LinkedList<Pattern> patterns = new LinkedList<Pattern>();
		for (iPart=0; iPart<len; iPart++) {
			sbRegex.setLength(0);
			String part = arrPart[iPart];
			String[] arrRecently = part.split(paramRecently);
			for(String recently : arrRecently) {
				recently = Pattern.quote(recently);
				sbRegex.append(recently).append(reRecently);
			}
			sbRegex.delete(sbRegex.length()-reRecently.length(), sbRegex.length());
			if (iPart==0) {//first regex
				if (part.length()==0)
					continue;
				else
					sbRegex.insert(0, "\\A");
			}
			else if (iPart == len-1) {//last regex
				if (part.length()==0)
					continue;
				else
					sbRegex.append("\\z");
			}
			//patterns[i] = Pattern.compile(sbRegex.toString());
			patterns.add( Pattern.compile(sbRegex.toString()) );
		}
		/* remove the first and last part:
		 *		a. for empty one(s), remove it
		 *		b. for non-empty one(s), find them and remove matched substring from *act* string  
		 */
		/*
		MatchRange rngFirst = null, rngLast = null;		
		Pattern pattern = patterns.pollFirst();
		if (pattern != null) {
			
		}
		patterns.pollLast();
		*/
		//find
		/*
		List<MatchRange> mrs = matchByRegex(patterns.toArray(new Pattern[1]), 0, act); 
		return mrs.size()==0;
		*/
		RegexMatcher regexMatcher = new RegexMatcher();
		boolean matched = regexMatcher.match(patterns.toArray(new Pattern[1]), act);
		//TODO:add <###></###> to act & exp
		//act
		List<MatchRange> rngs = regexMatcher.bestMatchedRanges; 
		StringBuffer sbAct = new StringBuffer(act);
		//StringBuffer sbExp = new StringBuffer();
		for (int i=rngs.size()-1; i>=0; i--) {
			MatchRange rng = rngs.get(i);
			/*if (rng.end == sbAct.length()) {
				sbAct.append("###>");
			}
			else {*/
			sbAct.insert(rng.start+rng.len, "===>");
			//}
			sbAct.insert(rng.start, "<===");
			
		}
		result.setResponse(sbAct.toString());
		//exp
		StringBuffer sbExp = new StringBuffer();
		int matchedPart = rngs.size();
		if ( arrPart[0].length()==0 ){
			matchedPart++;
		}
		for (int i=0; i<arrPart.length; i++) {
			if (i<matchedPart)
				sbExp.append("<===").append(arrPart[i]).append("===>");
			else
				sbExp.append(arrPart[i]);
			sbExp.append(paramAny);
		}
		sbExp.delete(sbExp.length()-paramAny.length(), sbExp.length());
		result.setExpectedResponse(sbExp.toString());
		return !matched;
	}
	
	private static void locateMismatchByChar(Result result) {
		String exp = result.getExpectedResponse(), act = result.getResponse();
		int expLen = exp.length(), actLen = act.length();
		int misExpStart = -1, misExpEnd = expLen - 1, misActStart=-1, misActEnd=actLen - 1;
		for (int i=0; i<expLen && i<actLen; i++) {
			if (exp.charAt(i)==act.charAt(i)) continue;
			//String sub = act.substring(i);
			misExpStart = i;
			misExpEnd = misExpStart + 10;
			int minLen = Math.min(expLen, actLen); 
			if (misExpEnd>=minLen) {
				misExpEnd = minLen-1;
				misExpStart = misExpEnd-10;
			}
			misActStart = misExpStart;
			misActEnd = misExpEnd;
			/*
			Matcher matcher = STOP_CHAR_PATTERN.matcher(sub);
			if (matcher.matches()) misEnd = matcher.start();*/
			break;
		}
		if (misExpStart<0 && expLen!=actLen) {
			if (expLen>actLen) {
				misExpStart = actLen;
				misExpEnd = expLen - 1;
			}
			else {
				misActStart = expLen;
				misActEnd = actLen - 1;
			}
		}
		if (misExpStart>=0 && misExpEnd>=misExpStart) {
			exp = exp.substring(0, misExpStart) + "<@@@" + exp.substring(misExpStart, misExpEnd+1) + "@@@>" + exp.substring(misExpEnd+1);
			result.setExpectedResponse(exp);
		}
		if ( misActStart>=0 && misActEnd>=misActStart ) {
			act = act.substring(0, misActStart) + "<@@@" + act.substring(misActStart, misActEnd+1) + "@@@>" + act.substring(misActEnd+1);
			result.setResponse(act);
		}
	}
	
	public static void main(String[] argv) {
		/*
		String act = "I am from \\ a * place ^, hei $ hia.";
		String exp = "<%_ANY_%>from<%_ANY_%>a * place ^<%_ANY_%>";
		System.out.println(act);
		System.out.println(exp);
		StringBuffer sbRegex = new StringBuffer();
		String paramAny = Pattern.quote("<%_ANY_%>");
		String reAny = ".*";
		String[] blocks = exp.split(paramAny);
		for (String block : blocks) {
			block = Pattern.quote(block);
			sbRegex.append(block).append(reAny);
		}
		sbRegex.delete(sbRegex.length()-reAny.length(), sbRegex.length());
		String regex = sbRegex.toString();
		System.out.println(regex);
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(act);
		for (int i=0;i<3; i++) {
			if (matcher.find()) {
				System.out.println("start=" + matcher.start());// + ", group=" + matcher.group(1));
			}
			else {
				System.out.println("no matched :(");
			}
		}
		*/
		Result result = new Result();
		StringBuffer sbAct = new StringBuffer();
		StringBuffer sbExp = new StringBuffer();
		try {
			FileReader frAct = new FileReader("C:\\apitest_act_json.txt");
			BufferedReader brAct = new BufferedReader(frAct);
			String line;
			while ((line=brAct.readLine())!=null) {
				sbAct.append(line).append('\n');
			}
			FileReader frExp = new FileReader("C:\\apitest_exp_json.txt");
			BufferedReader brExp = new BufferedReader(frExp);
			while ((line=brExp.readLine())!=null) {
				sbExp.append(line).append('\n');
			}
		} catch (Exception e) {
			System.out.println(e);
		}
		result.setResponse(sbAct.toString());
		result.setExpectedResponse(sbExp.toString());
		ResultEvaluator evaluator = new JSonResultEvaluator();
		evaluator.evaluate(result, 200, result.getExpectedResponse(), result.getResponse(), new LinkedList());
		//result.setResponse("{\"containsUnauthorizedCells\":false,\"axes\":[{\"positions\":[{\"members\":[{\"name\":\"2006.01\",\"dimension\":{\"name\":\"TIME\"},\"hierarchy\":{\"name\":\"PARENTH1\"},\"isLeaf\":true,\"displayName\":\"Jan 2006\",\"memberSetFormulaIndex\":0,\"childrenCount\":0}]}]},{\"positions\":[{\"members\":[{\"name\":\"a 01\",\"dimension\":{\"name\":\"S_ACCOUNT\"},\"hierarchy\":{\"name\":\"PARENTH4\"},\"isLeaf\":false,\"displayName\":\"a 01\",\"memberSetFormulaIndex\":0,\"childrenCount\":3}]},{\"members\":[{\"name\":\"A- 01\",\"dimension\":{\"name\":\"S_ACCOUNT\"},\"hierarchy\":{\"name\":\"PARENTH4\"},\"isLeaf\":true,\"displayName\":\"A- 01\",\"memberSetFormulaIndex\":0,\"childrenCount\":0}]},{\"members\":[{\"name\":\"B01\",\"dimension\":{\"name\":\"S_ACCOUNT\"},\"hierarchy\":{\"name\":\"PARENTH4\"},\"isLeaf\":true,\"displayName\":\"B01\",\"memberSetFormulaIndex\":0,\"childrenCount\":0}]},{\"members\":[{\"name\":\"B01-\",\"dimension\":{\"name\":\"S_ACCOUNT\"},\"hierarchy\":{\"name\":\"PARENTH4\"},\"isLeaf\":true,\"displayName\":\"B01-\",\"memberSetFormulaIndex\":0,\"childrenCount\":0}]}]},{\"positions\":[{\"members\":[{\"name\":\"Actual\",\"dimension\":{\"name\":\"CATEGORY\"},\"hierarchy\":{\"name\":\"\"},\"isLeaf\":false,\"displayName\":\"Actual\",\"memberSetFormulaIndex\":0,\"childrenCount\":0},{\"name\":\"AR\",\"dimension\":{\"name\":\"ENTITY\"},\"hierarchy\":{\"name\":\"PARENTH1\"},\"isLeaf\":false,\"displayName\":\"Argentina\",\"memberSetFormulaIndex\":0,\"childrenCount\":0},{\"name\":\"PERIODIC\",\"dimension\":{\"name\":\"MEASURES\"},\"hierarchy\":{\"name\":\"\"},\"isLeaf\":false,\"displayName\":\"Periodic\",\"memberSetFormulaIndex\":0,\"childrenCount\":0}]}]}],\"cells\":[{\"hasValue\":true,\"comments\":[],\"isCommentEditable\":true,\"isValueEditable\":false,\"isValueReadable\":true,\"value\":20.0000000},{\"hasValue\":true,\"comments\":[],\"isCommentEditable\":true,\"isValueEditable\":true,\"isValueReadable\":true,\"value\":20.0000000},{\"hasValue\":false,\"comments\":[],\"isCommentEditable\":true,\"isValueEditable\":true,\"isValueReadable\":true,\"value\":0.0000000},{\"hasValue\":false,\"comments\":[],\"isCommentEditable\":true,\"isValueEditable\":true,\"isValueReadable\":true,\"value\":0.0000000}]}");
		//result.setExpectedResponse("<%_ANY_%>[{\"name\":\"2006.01\",\"dimension\":{\"name\":\"TIME\"},\"hierarchy<%_ANY_%>");
		//result.setExpectedResponse("<%_ANY_%>[{\"name\":\"2006.01\"<%_ANY_%>TIME\"},\"hierarchy<%_ANY_%>");
		//result.setExpectedResponse("{\"containsUnauthorizedCells\":false,\"axes\":[{\"positions\":[{\"members\":[{\"name\":\"<%_RECENTLY_%>\"<%_ANY_%>TIME\"},\"hierarchy<%_ANY_%>0.0000000}]}");
		/*
		System.out.println(locateMismatchByRegex(result));
		System.out.println("----------------------------------------------------------------------------");
		System.out.println(result.getExpectedResponse());
		System.out.println("----------------------------------------------------------------------------");
		System.out.println(result.getResponse());
		*/
		/*
		String candidate =  
		     "A Matcher examines the results of applying a pattern.";  
		    String regex = "\\ba\\w*\\b";  
		    Pattern p = Pattern.compile(regex);  
		    Matcher m = p.matcher(candidate);  
		    String val = null;  
		    System.out.println("INPUT: " + candidate);  
		    System.out.println("REGEX: " + regex +"\r\n");  
		    while (m.find()){  
		      val = m.group();  
		      System.out.println("MATCH: " + val);  
		    }  
		    if (val == null) {  
		      System.out.println("NO MATCHES: ");  
		    }  
		    */
	}
}
